Add suspend/resume to devices owned by Xen.
authorkfraser@localhost.localdomain <kfraser@localhost.localdomain>
Mon, 11 Jun 2007 14:44:48 +0000 (15:44 +0100)
committerkfraser@localhost.localdomain <kfraser@localhost.localdomain>
Mon, 11 Jun 2007 14:44:48 +0000 (15:44 +0100)
Signed-off-by: Ke Yu <ke.yu@intel.com>
Signed-off-by: Kevin Tian <kevin.tian@intel.com>
Signed-off-by: Keir Fraser <keir@xensource.com>
13 files changed:
xen/arch/x86/apic.c
xen/arch/x86/i8259.c
xen/arch/x86/io_apic.c
xen/arch/x86/time.c
xen/drivers/char/console.c
xen/drivers/char/serial.c
xen/include/asm-x86/apic.h
xen/include/asm-x86/io_apic.h
xen/include/asm-x86/irq.h
xen/include/asm-x86/time.h
xen/include/xen/console.h
xen/include/xen/serial.h
xen/include/xen/time.h

index 4ec8d14726c8ecd5088e2132315db0ce010b70d6..f934a305060b03c82580fb66970b68b86d531132 100644 (file)
@@ -579,6 +579,95 @@ void __devinit setup_local_APIC(void)
     apic_pm_activate();
 }
 
+static struct {
+    int active;
+    /* r/w apic fields */
+    unsigned int apic_id;
+    unsigned int apic_taskpri;
+    unsigned int apic_ldr;
+    unsigned int apic_dfr;
+    unsigned int apic_spiv;
+    unsigned int apic_lvtt;
+    unsigned int apic_lvtpc;
+    unsigned int apic_lvt0;
+    unsigned int apic_lvt1;
+    unsigned int apic_lvterr;
+    unsigned int apic_tmict;
+    unsigned int apic_tdcr;
+    unsigned int apic_thmr;
+} apic_pm_state;
+
+int lapic_suspend(void)
+{
+    unsigned long flags;
+
+    if (!apic_pm_state.active)
+        return 0;
+
+    apic_pm_state.apic_id = apic_read(APIC_ID);
+    apic_pm_state.apic_taskpri = apic_read(APIC_TASKPRI);
+    apic_pm_state.apic_ldr = apic_read(APIC_LDR);
+    apic_pm_state.apic_dfr = apic_read(APIC_DFR);
+    apic_pm_state.apic_spiv = apic_read(APIC_SPIV);
+    apic_pm_state.apic_lvtt = apic_read(APIC_LVTT);
+    apic_pm_state.apic_lvtpc = apic_read(APIC_LVTPC);
+    apic_pm_state.apic_lvt0 = apic_read(APIC_LVT0);
+    apic_pm_state.apic_lvt1 = apic_read(APIC_LVT1);
+    apic_pm_state.apic_lvterr = apic_read(APIC_LVTERR);
+    apic_pm_state.apic_tmict = apic_read(APIC_TMICT);
+    apic_pm_state.apic_tdcr = apic_read(APIC_TDCR);
+    apic_pm_state.apic_thmr = apic_read(APIC_LVTTHMR);
+    
+    local_irq_save(flags);
+    disable_local_APIC();
+    local_irq_restore(flags);
+    return 0;
+}
+
+int lapic_resume(void)
+{
+    unsigned int l, h;
+    unsigned long flags;
+
+    if (!apic_pm_state.active)
+        return 0;
+
+    local_irq_save(flags);
+
+    /*
+     * Make sure the APICBASE points to the right address
+     *
+     * FIXME! This will be wrong if we ever support suspend on
+     * SMP! We'll need to do this as part of the CPU restore!
+     */
+    rdmsr(MSR_IA32_APICBASE, l, h);
+    l &= ~MSR_IA32_APICBASE_BASE;
+    l |= MSR_IA32_APICBASE_ENABLE | mp_lapic_addr;
+    wrmsr(MSR_IA32_APICBASE, l, h);
+
+    apic_write(APIC_LVTERR, ERROR_APIC_VECTOR | APIC_LVT_MASKED);
+    apic_write(APIC_ID, apic_pm_state.apic_id);
+    apic_write(APIC_DFR, apic_pm_state.apic_dfr);
+    apic_write(APIC_LDR, apic_pm_state.apic_ldr);
+    apic_write(APIC_TASKPRI, apic_pm_state.apic_taskpri);
+    apic_write(APIC_SPIV, apic_pm_state.apic_spiv);
+    apic_write(APIC_LVT0, apic_pm_state.apic_lvt0);
+    apic_write(APIC_LVT1, apic_pm_state.apic_lvt1);
+    apic_write(APIC_LVTTHMR, apic_pm_state.apic_thmr);
+    apic_write(APIC_LVTPC, apic_pm_state.apic_lvtpc);
+    apic_write(APIC_LVTT, apic_pm_state.apic_lvtt);
+    apic_write(APIC_TDCR, apic_pm_state.apic_tdcr);
+    apic_write(APIC_TMICT, apic_pm_state.apic_tmict);
+    apic_write(APIC_ESR, 0);
+    apic_read(APIC_ESR);
+    apic_write(APIC_LVTERR, apic_pm_state.apic_lvterr);
+    apic_write(APIC_ESR, 0);
+    apic_read(APIC_ESR);
+    local_irq_restore(flags);
+    return 0;
+}
+
+
 /*
  * If Linux enabled the LAPIC against the BIOS default
  * disable it down before re-entering the BIOS on shutdown.
@@ -602,7 +691,10 @@ void lapic_shutdown(void)
     local_irq_restore(flags);
 }
 
-static void apic_pm_activate(void) { }
+static void apic_pm_activate(void)
+{
+    apic_pm_state.active = 1;
+}
 
 /*
  * Detect and enable local APICs on non-SMP boards.
index 76c47b559235afba1f48b1bfbba55a295b12faae..2069319ce401207d1938d290219adc40a4983305 100644 (file)
@@ -306,6 +306,36 @@ static void mask_and_ack_8259A_vector(unsigned int vector)
     }
 }
 
+static char irq_trigger[2];
+/**
+ * ELCR registers (0x4d0, 0x4d1) control edge/level of IRQ
+ */
+static void restore_ELCR(char *trigger)
+{
+    outb(trigger[0], 0x4d0);
+    outb(trigger[1], 0x4d1);
+}
+
+static void save_ELCR(char *trigger)
+{
+    /* IRQ 0,1,2,8,13 are marked as reserved */
+    trigger[0] = inb(0x4d0) & 0xF8;
+    trigger[1] = inb(0x4d1) & 0xDE;
+}
+
+int i8259A_resume(void)
+{
+    init_8259A(0);
+    restore_ELCR(irq_trigger);
+    return 0;
+}
+
+int i8259A_suspend(void)
+{
+    save_ELCR(irq_trigger);
+    return 0;
+}
+
 void __init init_8259A(int auto_eoi)
 {
     unsigned long flags;
index d56351e7aa3ce944e05b107bab608eeaa02442d0..ebd177deb1e2481e2da9d6fb1342c45fd95e10b8 100644 (file)
@@ -1793,6 +1793,80 @@ void __init setup_IO_APIC(void)
     register_keyhandler('z', print_IO_APIC_keyhandler, "print ioapic info");
 }
 
+struct IO_APIC_route_entry *ioapic_pm_state=NULL;
+
+void ioapic_pm_state_alloc(void)
+{
+    int i, nr_entry = 0;
+
+    if (ioapic_pm_state != NULL)
+        return;
+
+    for (i = 0; i < nr_ioapics; i++)
+        nr_entry += nr_ioapic_registers[i];
+
+    ioapic_pm_state = _xmalloc(sizeof(struct IO_APIC_route_entry)*nr_entry,
+                               sizeof(struct IO_APIC_route_entry));
+}
+
+int ioapic_suspend(void)
+{
+    struct IO_APIC_route_entry *entry;
+    unsigned long flags;
+    int apic,i;
+
+    ioapic_pm_state_alloc();
+
+    if (ioapic_pm_state == NULL) {
+        printk("Cannot suspend ioapic due to lack of memory\n");
+        return 1;
+    }
+
+    entry = ioapic_pm_state;
+
+    spin_lock_irqsave(&ioapic_lock, flags);
+    for (apic = 0; apic < nr_ioapics; apic++) {
+        for (i = 0; i < nr_ioapic_registers[apic]; i ++, entry ++ ) {
+            *(((int *)entry) + 1) = io_apic_read(apic, 0x11 + 2 * i);
+            *(((int *)entry) + 0) = io_apic_read(apic, 0x10 + 2 * i);
+        }
+    }
+    spin_unlock_irqrestore(&ioapic_lock, flags);
+
+    return 0;
+}
+
+int ioapic_resume(void)
+{
+    struct IO_APIC_route_entry *entry;
+    unsigned long flags;
+    union IO_APIC_reg_00 reg_00;
+    int i,apic;
+    
+    if (ioapic_pm_state == NULL){
+        printk("Cannot resume ioapic due to lack of memory\n");
+        return 1;
+    }
+    
+    entry = ioapic_pm_state;
+
+    spin_lock_irqsave(&ioapic_lock, flags);
+    for (apic = 0; apic < nr_ioapics; apic++){
+        reg_00.raw = io_apic_read(apic, 0);
+        if (reg_00.bits.ID != mp_ioapics[apic].mpc_apicid) {
+            reg_00.bits.ID = mp_ioapics[apic].mpc_apicid;
+            io_apic_write(apic, 0, reg_00.raw);
+        }
+        for (i = 0; i < nr_ioapic_registers[apic]; i++, entry++) {
+            io_apic_write(apic, 0x11+2*i, *(((int *)entry)+1));
+            io_apic_write(apic, 0x10+2*i, *(((int *)entry)+0));
+        }
+    }
+    spin_unlock_irqrestore(&ioapic_lock, flags);
+
+    return 0;
+}
+
 /* --------------------------------------------------------------------------
                           ACPI-based IOAPIC Configuration
    -------------------------------------------------------------------------- */
index 29e82e37d8ddc78181d14f1f7466498f0ffc62c7..81e5598eaea40565a3190a17c2f274eef5d0190a 100644 (file)
@@ -171,11 +171,18 @@ static struct irqaction irq0 = { timer_interrupt, "timer", NULL};
 #define CALIBRATE_FRAC  20      /* calibrate over 50ms */
 #define CALIBRATE_LATCH ((CLOCK_TICK_RATE+(CALIBRATE_FRAC/2))/CALIBRATE_FRAC)
 
-static u64 calibrate_boot_tsc(void)
+static u64 init_pit_and_calibrate_tsc(void)
 {
     u64 start, end;
     unsigned long count;
 
+    /* Set PIT channel 0 to HZ Hz. */
+#define CLOCK_TICK_RATE 1193180 /* crystal freq (Hz) */
+#define LATCH (((CLOCK_TICK_RATE)+(HZ/2))/HZ)
+    outb_p(0x34, PIT_MODE);        /* binary, mode 2, LSB/MSB, ch 0 */
+    outb_p(LATCH & 0xff, PIT_CH0); /* LSB */
+    outb(LATCH >> 8, PIT_CH0);     /* MSB */
+
     /* Set the Gate high, disable speaker */
     outb((inb(0x61) & ~0x02) | 0x01, 0x61);
 
@@ -489,11 +496,12 @@ static s_time_t read_platform_stime(void)
 {
     u64 count;
     s_time_t stime;
+    unsigned long flags;
 
-    spin_lock_irq(&platform_timer_lock);
+    spin_lock_irqsave(&platform_timer_lock, flags);
     count = plt_count64 + ((plt_src.read_counter() - plt_count) & plt_mask);
     stime = __read_platform_stime(count);
-    spin_unlock_irq(&platform_timer_lock);
+    spin_unlock_irqrestore(&platform_timer_lock, flags);
 
     return stime;
 }
@@ -502,13 +510,21 @@ static void platform_time_calibration(void)
 {
     u64 count;
     s_time_t stamp;
+    unsigned long flags;
 
-    spin_lock_irq(&platform_timer_lock);
+    spin_lock_irqsave(&platform_timer_lock, flags);
     count = plt_count64 + ((plt_src.read_counter() - plt_count) & plt_mask);
     stamp = __read_platform_stime(count);
     stime_platform_stamp = stamp;
     platform_timer_stamp = count;
-    spin_unlock_irq(&platform_timer_lock);
+    spin_unlock_irqrestore(&platform_timer_lock, flags);
+}
+
+static void resume_platform_timer(void)
+{
+    /* No change in platform_stime across suspend/resume. */
+    platform_timer_stamp = plt_count64;
+    plt_count = plt_src.read_counter();
 }
 
 static void init_platform_timer(void)
@@ -875,7 +891,7 @@ void init_percpu_time(void)
 
     local_irq_save(flags);
     rdtscll(t->local_tsc_stamp);
-    now = (smp_processor_id() == 0) ? 0 : read_platform_stime();
+    now = !plt_src.read_counter ? 0 : read_platform_stime();
     local_irq_restore(flags);
 
     t->stime_master_stamp = now;
@@ -907,9 +923,9 @@ int __init init_xen_time(void)
 /* Early init function. */
 void __init early_time_init(void)
 {
-    u64 tmp = calibrate_boot_tsc();
+    u64 tmp = init_pit_and_calibrate_tsc();
 
-    set_time_scale(&per_cpu(cpu_time, 0).tsc_scale, tmp);
+    set_time_scale(&this_cpu(cpu_time).tsc_scale, tmp);
 
     do_div(tmp, 1000);
     cpu_khz = (unsigned long)tmp;
@@ -931,6 +947,33 @@ unsigned long get_localtime(struct domain *d)
         + d->time_offset_seconds;
 }
 
+int time_suspend(void)
+{
+    /* Better to cancel calibration timer for accuracy. */
+    kill_timer(&this_cpu(cpu_time).calibration_timer);
+
+    return 0;
+}
+
+int time_resume(void)
+{
+    u64 now_sec, tmp = init_pit_and_calibrate_tsc();
+
+    set_time_scale(&this_cpu(cpu_time).tsc_scale, tmp);
+
+    resume_platform_timer();
+    now_sec = read_platform_stime();
+    do_div(now_sec, SECONDS(1));
+    wc_sec = get_cmos_time() - now_sec;
+
+    init_percpu_time();
+
+    if ( !is_idle_vcpu(current) )
+        update_vcpu_system_time(current);
+
+    return 0;
+}
+
 /*
  * Local variables:
  * mode: C
index 33ad7aaaf1b711dfc3dc9ebab03f40ffe921832c..6d1d40beee1968e2dcd2aa1b730e98a2dcbdcc29 100644 (file)
@@ -848,7 +848,6 @@ __initcall(debugtrace_init);
 #endif /* !NDEBUG */
 
 
-
 /*
  * **************************************************************
  * *************** Debugging/tracing/error-report ***************
@@ -914,6 +913,30 @@ void __warn(char *file, int line)
     dump_execution_state();
 }
 
+
+/*
+ * **************************************************************
+ * ****************** Console suspend/resume ********************
+ * **************************************************************
+ */
+
+static void suspend_steal_fn(const char *str) { }
+static int suspend_steal_id;
+
+int console_suspend(void)
+{
+    suspend_steal_id = console_steal(sercon_handle, suspend_steal_fn);
+    serial_suspend();
+    return 0;
+}
+
+int console_resume(void)
+{
+    serial_resume();
+    console_giveback(suspend_steal_id);
+    return 0;
+}
+
 /*
  * Local variables:
  * mode: C
index 8ec6ca6bd23a1d53726e66375acb88f78af109a8..bb196ace109d98092f4e25848b976207d040ff7c 100644 (file)
@@ -381,6 +381,20 @@ int serial_irq(int idx)
     return -1;
 }
 
+void serial_suspend(void)
+{
+    int i, irq;
+    for ( i = 0; i < ARRAY_SIZE(com); i++ )
+        if ( (irq = serial_irq(i)) >= 0 )
+            free_irq(irq);
+}
+
+void serial_resume(void)
+{
+    serial_init_preirq();
+    serial_init_postirq();
+}
+
 void serial_register_uart(int idx, struct uart_driver *driver, void *uart)
 {
     /* Store UART-specific info. */
index 090465c5196f2cc7988497feb5a1fbacdb7fcaf7..fd0ee81a017cc7236a67ec457f43adcc30286c13 100644 (file)
@@ -108,6 +108,8 @@ extern void nmi_watchdog_tick (struct cpu_user_regs *regs);
 extern int APIC_init_uniprocessor (void);
 extern void disable_APIC_timer(void);
 extern void enable_APIC_timer(void);
+extern int lapic_suspend(void);
+extern int lapic_resume(void);
 
 extern int check_nmi_watchdog (void);
 extern void enable_NMI_through_LVT0 (void * dummy);
@@ -123,6 +125,8 @@ extern unsigned int nmi_watchdog;
 
 #else /* !CONFIG_X86_LOCAL_APIC */
 static inline void lapic_shutdown(void) { }
+static inline int lapic_suspend(void) {return 0;}
+static inline int lapic_resume(void) {return 0;}
 
 #endif /* !CONFIG_X86_LOCAL_APIC */
 
index f256f5cd19184d368f0c9889dec0d6aa9b4f9df1..b8731bf2785450f36bff06215e6ff8c2f38595dd 100644 (file)
@@ -169,9 +169,13 @@ extern int timer_uses_ioapic_pin_0;
 #endif /*CONFIG_ACPI_BOOT*/
 
 extern int (*ioapic_renumber_irq)(int ioapic, int irq);
+extern int ioapic_suspend(void);
+extern int ioapic_resume(void);
 
 #else  /* !CONFIG_X86_IO_APIC */
 #define io_apic_assign_pci_irqs 0
+static inline int ioapic_suspend(void) {return 0};
+static inline int ioapic_resume(void) {return 0};
 #endif
 
 extern int assign_irq_vector(int irq);
index 33aff7150c4ea32c5f9779ba8a435acc018a1691..061c497616d913371ac911f11bcae393e87643e7 100644 (file)
@@ -35,6 +35,8 @@ void disable_8259A_irq(unsigned int irq);
 void enable_8259A_irq(unsigned int irq);
 int i8259A_irq_pending(unsigned int irq);
 void init_8259A(int aeoi);
+int i8259A_suspend(void);
+int i8259A_resume(void);
 
 void setup_IO_APIC(void);
 void disable_IO_APIC(void);
index df74cf2962b80ea9cc72a220b4a8bf113a94ec5d..85bc78bfbd73eb917343bcd4c17ad976f7fe355b 100644 (file)
@@ -21,4 +21,9 @@ mktime (unsigned int year, unsigned int mon,
         unsigned int day, unsigned int hour,
         unsigned int min, unsigned int sec);
 
+extern int time_suspend(void);
+extern int time_resume(void);
+
+extern void init_percpu_time(void);
+
 #endif /* __X86_TIME_H__ */
index c01dbf23ad815fdcd5adb2db6981e4c979538eab..d6c0510281c29c6d985cdfcf97b43b0e8cdd0f47 100644 (file)
@@ -38,4 +38,7 @@ int console_steal(int handle, void (*fn)(const char *));
 /* Give back stolen console. Takes the identifier returned by console_steal. */
 void console_giveback(int id);
 
+int console_suspend(void);
+int console_resume(void);
+
 #endif /* __CONSOLE_H__ */
index 7bae140712f9f11e1e71bc0a3c542bd792a633e9..75a07c93395bd04addd72c7ef8ba7518d2beb64d 100644 (file)
@@ -104,6 +104,10 @@ int serial_tx_space(int handle);
 /* Return irq number for specified serial port (identified by index). */
 int serial_irq(int idx);
 
+/* Serial suspend/resume. */
+void serial_suspend(void);
+void serial_resume(void);
+
 /*
  * Initialisation and helper functions for uart drivers.
  */
index 33b12763ed4701edff693f87745783a0e2e40179..0c7c80ebde845a4f69ce80cf615e657515890cfb 100644 (file)
@@ -31,7 +31,6 @@
 #include <asm/time.h>
 
 extern int init_xen_time(void);
-extern void init_percpu_time(void);
 
 extern unsigned long cpu_khz;